package dcedit.util;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.border.*;


/** class FindReplaceDialog */
public class FindReplaceDialog extends JDialog implements ActionListener
{
   /** Serializable constant */
   public static final long serialVersionUID = 1283884708551L;

   /** Layout constants */
   private final static int TEXT_FIELD_SIZE = 20;
   private final static int COMPONENT_GAP = 10;
   private final static Border EMPTY_BORDER = new EmptyBorder(5, 5, 5, 5);

   /** Shared instance of this class */
   private static FindReplaceDialog sharedFindReplace;

   /** Visible components on the dialog */
   private JLabel findLabel;
   private JLabel replaceLabel;
   private JTextField findData;
   private JTextField replaceData;
   private JCheckBox matchCase;
   private JCheckBox matchWord;
   private JRadioButton searchUp;
   private JRadioButton searchDown;
   private JButton findNextButton;
   private JButton closeButton;
   private JButton replaceButton;
   private JButton replaceAllButton;
   private JPanel findPanel;
   private JPanel replacePanel;
   private JPanel optionPanel;
   private JPanel commandPanel;

   /** Component to search */
   private DCTextEditor textComponent;

   /** Starting position of search */
   private Position searchStartPosition;

   /** Has the search wrapped */
   private boolean searchWrap;

   /** Constructor */
   public FindReplaceDialog(Frame owner) {
      super(owner);

      setDefaultCloseOperation( JDialog.HIDE_ON_CLOSE );
      setResizable(false);

      // Create find panel
      findData = new JTextField(TEXT_FIELD_SIZE);
      findData.setMaximumSize(findData.getPreferredSize());

      findLabel = new JLabel("Find what:");
      findLabel.setDisplayedMnemonic('N');
      findLabel.setLabelFor(findData);

      findPanel = new JPanel();
      findPanel.setBorder(EMPTY_BORDER);
      findPanel.setLayout(new BoxLayout(findPanel, BoxLayout.X_AXIS));
      findPanel.add(findLabel);
      findPanel.add(Box.createHorizontalGlue());
      findPanel.add(Box.createHorizontalStrut(COMPONENT_GAP));
      findPanel.add(findData);

      // Create replace panel
      replaceData = new JTextField(TEXT_FIELD_SIZE);
      replaceData.setMaximumSize(findData.getPreferredSize());

      replaceLabel = new JLabel("Replace with:");
      replaceLabel.setDisplayedMnemonic('P');
      replaceLabel.setLabelFor(replaceData);

      replacePanel = new JPanel();
      replacePanel.setBorder(EMPTY_BORDER);
      replacePanel.setLayout(new BoxLayout(replacePanel, BoxLayout.X_AXIS));
      replacePanel.add(replaceLabel);
      replacePanel.add(Box.createHorizontalGlue());
      replacePanel.add(Box.createHorizontalStrut(COMPONENT_GAP));
      replacePanel.add(replaceData);

      // Create options panel
      JPanel matchPanel = new JPanel();
      matchPanel.setLayout(new GridLayout(2, 1));
      matchCase = new JCheckBox("Match case");
      matchCase.setMnemonic('C');
      matchWord = new JCheckBox("Match word");
      matchWord.setMnemonic('W');
      matchPanel.add(matchCase);
      matchPanel.add(matchWord);

      JPanel searchPanel = new JPanel();
      searchPanel.setLayout(new GridLayout(2, 1));
      searchDown = new JRadioButton("Search Down");
      searchDown.setMnemonic('D');
      searchDown.setSelected(true);
      searchUp = new JRadioButton("Search Up");
      searchUp.setMnemonic('U');
      searchPanel.add(searchDown);
      searchPanel.add(searchUp);

      ButtonGroup searchGroup = new ButtonGroup();
      searchGroup.add(searchDown);
      searchGroup.add(searchUp);

      optionPanel = new JPanel();
      optionPanel.setLayout(new GridLayout(1, 2));
      optionPanel.setBorder(new TitledBorder("Options"));
      optionPanel.add(matchPanel);
      optionPanel.add(searchPanel);

      // Create command panel
      commandPanel = new JPanel();
      findNextButton = createButton(commandPanel, "Find Next", 'F');
      replaceButton = createButton(commandPanel, "Replace", 'R');
      replaceAllButton = createButton(commandPanel, "Replace All", 'a');
      closeButton = createButton(commandPanel, "Close", ' ');
      closeButton.setEnabled(true);

      // Layout all the panels to build the dialog
      JPanel panel = new JPanel();
      panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
      panel.setBorder(EMPTY_BORDER);
      panel.add(findPanel);
      panel.add(replacePanel);
      panel.add(optionPanel);
      panel.add(commandPanel);
      setContentPane(panel);

      // Set the default button for the dialog
      getRootPane().setDefaultButton(findNextButton);

      // Enable/Disable buttons on the command panel as find text is entered/deleted
      findData.addKeyListener(new KeyAdapter() {
         public void keyReleased(KeyEvent e) {
            // Don't reset search variable when enter is entered
            if(e.getKeyCode() != KeyEvent.VK_ENTER) {
               boolean state = (findData.getDocument().getLength() > 0);
               findNextButton.setEnabled(state);
               replaceButton.setEnabled(state);
               replaceAllButton.setEnabled(state);
               resetSearchVariables();
            }
         }
      });

      // Handle escape key
      KeyStroke escapeKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
      Action escapeAction = new AbstractAction() {
         public static final long serialVersionUID = 1283884795007L;

         public void actionPerformed(ActionEvent e) {
            processClose();
         }
      };

      getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escapeKeyStroke, "ESCAPE");
      getRootPane().getActionMap().put("ESCAPE", escapeAction);
   } // END constructor

   /** getSharedInstance method */
   public static FindReplaceDialog getSharedInstance(Frame owner) {
      if(sharedFindReplace == null) {
         sharedFindReplace = new FindReplaceDialog(owner);
         sharedFindReplace.setLocationRelativeTo(owner);
      }

      return sharedFindReplace;
   } // END getSharedInstance

   /** showFind method */
   public void showFind(DCTextEditor textComponent) {
      setTitle("Find");
      setTextComponent(textComponent);
      showReplaceComponents(false);
      pack();
      setVisible(true);
      findData.requestFocus();
      resetSearchVariables();
   } // END showFind

   /** doFindNext method */
   public void doFindNext(DCTextEditor textComponent) {
      if(findData.getText().length() == 0) {
         showFind(textComponent);
      } else {
         setTextComponent(textComponent);
         processFindNext();
      }
   } // END doFindNext

   /** showReplace method */
   public void showReplace(DCTextEditor textComponent) {
      setTitle("Find and Replace");
      setTextComponent(textComponent);
      showReplaceComponents(true);
      pack();
      setVisible(true);
      findData.requestFocus();
      resetSearchVariables();
   } // END showReplace

   /** showReplaceComponents method */
   private void showReplaceComponents(boolean value) {
      replacePanel.setVisible(value);
      replaceButton.setVisible(value);
      replaceAllButton.setVisible(value);
   } // END showReplaceComponents

   /** actionPerformed method */
   public void actionPerformed(ActionEvent e) {
      Object o = e.getSource();
      if(o == findNextButton) processFindNext();
      if(o == replaceButton) processReplace();
      if(o == replaceAllButton) processReplaceAll();
      if(o == closeButton) processClose();
   } // END actionPerformed

   /** createButton method */
   private JButton createButton(JPanel panel, String label, char mnemonic) {
      JButton button = new JButton(label);
      button.setMnemonic(mnemonic);
      button.setEnabled(false);
      button.addActionListener(this);
      panel.add(button);
      return button;
   } // END createButton

   /** processFindNext method */
   private boolean processFindNext() {
      // Set variables required for the search
      String needle = findData.getText();
      String haystack = textComponent.getText();
      int offset = textComponent.getTextArea().getSelectionStart();
      String selectedText = textComponent.getTextArea().getSelectedText();
      textComponent.getTextArea().setSelectionEnd(offset); // deselect text

      // Simplify search when you don't care about case
      if(!matchCase.isSelected()) {
         haystack = haystack.toLowerCase();
         needle = needle.toLowerCase();
      }

      // When text is already selected,
      // change offset so we don't find the same text again
      if(needle.equalsIgnoreCase(selectedText)) {
         if(searchDown()) {
            offset++;
         } else {
            offset--;
         }
      }

      // Search for the string and show the result
      int result = searchFor(needle, haystack, offset);

      if(result == -1) {
         JOptionPane.showMessageDialog(this, "Finished searching the document.", "Find", JOptionPane.INFORMATION_MESSAGE);
         resetSearchVariables();
         return false;
      } else {
         textComponent.getTextArea().setSelectionStart(result);
         textComponent.getTextArea().setSelectionEnd(result + needle.length());
         return true;
      }
   } // END processFindNext

   /** searchDown method */
   private boolean searchDown()
   {
      return searchDown.isSelected();
   } // END searchDown

   /** searchFor method */
   private int searchFor(String needle, String haystack, int offset) {
      int result;
      int wrapSearchOffset;

      if(searchDown()) {
         wrapSearchOffset = 0;
         result = haystack.indexOf(needle, offset);
      } else {
         wrapSearchOffset = haystack.length();
         result = haystack.lastIndexOf(needle, offset);
      } // END if/else

      // String not found,
      // Attempt to wrap and continue the search
      if(result == -1) {
         if(searchWrap) {
            return result;
         } else {
            searchWrap = true;
            return searchFor(needle, haystack, wrapSearchOffset);
         } // END if/else
      } // END if

      // String was found,
      // Make sure we haven't wrapped and passed the original start position
      int wrapResult;

      if(searchDown()) {
         wrapResult = result - searchStartPosition.getOffset();
      } else {
         wrapResult = searchStartPosition.getOffset() - result - 1;
      } // END if/else

      if(searchWrap && (wrapResult >= 0)) return -1;

      // String was found,
      // see if it is a word
      if(matchWord.isSelected() && !isWord(haystack, result, needle.length())) {
         if(searchDown()) {
            return searchFor(needle, haystack, result + 1);
         } else {
            return searchFor(needle, haystack, result - 1);
         } // END if/else
      } // END if

      // Subtract new lines
      result -= Util.countNewLines(haystack.substring(0, result));

      // The search was successfull
      return result;
   } // END searchFor

   /** isWord method */
   private boolean isWord(String haystack, int offset, int length) {
      int leftSide = offset - 1;
      int rightSide = offset + length;

      if(isDelimiter(haystack, leftSide) && isDelimiter(haystack, rightSide)) return true;
      else return false;
   } // END isWord

   /** isDelimiter method */
   private boolean isDelimiter(String haystack, int offset) {
      if((offset < 0) || (offset > haystack.length())) return true;
      return !Character.isLetterOrDigit(haystack.charAt(offset));
   } // END isDelimiter

   /** processReplace method */
   private boolean processReplace() {
      String needle = findData.getText();
      String replaceText = replaceData.getText();
      String selectedText = textComponent.getTextArea().getSelectedText();

      if(matchCase.isSelected() && needle.equals(selectedText)) {
         textComponent.getTextArea().replaceSelection(replaceText);
      }

      if(!matchCase.isSelected() && needle.equalsIgnoreCase(selectedText)) {
         textComponent.getTextArea().replaceSelection(replaceText);
      }

      // Update tab title text
      final String TAB_TEXT = Util.getSelectedTabText();
      if(!TAB_TEXT.endsWith(Constants.ASTERISK_CHAR)) {
         Util.setSelectedTabText(TAB_TEXT + Constants.ASTERISK_CHAR);
      } // END if no asterisk

      return processFindNext();
   } // END processReplace

   /** processReplaceAll method */
   private void processReplaceAll() {
      JViewport viewport = null;
      Point point = null;

      resetSearchVariables();

      Container c = textComponent.getParent();

      if(c instanceof JViewport) {
         viewport = (JViewport)c;
         point = viewport.getViewPosition();
      }

      while(processReplace());

      if(c instanceof JViewport) {
         viewport.setViewPosition(point);
      }
   } // END processReplaceAll

   /** processClose method */
   private void processClose() {
      setVisible(false);
   } // END processClose

   /** setTextComponent method */
   private void setTextComponent(DCTextEditor textComponent) {
      if(this.textComponent != textComponent) {
         this.textComponent = textComponent;
         resetSearchVariables();
      }
   } // END setTextComponent

   /** resetSearchVariables method */
   private void resetSearchVariables() {
      try {
         searchWrap = false;
         searchStartPosition = textComponent.getDocument().createPosition(textComponent.getTextArea().getSelectionStart());
      } catch(BadLocationException e) {
         System.out.println(e);
      }
   } // END resetSearchVariables
} // END class FindReplaceDialog